﻿using System;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using Seagull.Configuration;
using Seagull.DAL.Utils;
using Seagull.Events;
using Seagull.Events.Data;
using Seagull.Model;

namespace Seagull.DAL
{
    /// <summary>
    /// 实时数据接收模块
    /// </summary>
    public class RealtimeFrameSetSource : IDisposable
    {
        #region Singleton

        private static readonly Lazy<RealtimeFrameSetSource> _instance =
            new Lazy<RealtimeFrameSetSource>(() => new RealtimeFrameSetSource());

        public static RealtimeFrameSetSource Instance
        {
            get { return _instance.Value; }
        }

        #endregion

        private readonly FrameDataCoercer[] _frameDataNoiseCancellers;
        private readonly IPhaseShifter[] _phaseShifters;
        private UdpClient _udpClient;

        /// <summary>
        ///     Only accessed by udp receiving thread.
        /// </summary>
        private readonly FrameSet[] _unfinishedFrameSets;

        // TODO: 改为使用简单的bool
        private CancellationTokenSource _cancellationTokenSource;
        private Thread _dataReceivingThread;

        private RealtimeFrameSetSource()
        {
            _unfinishedFrameSets = new FrameSet[Config.Instance.ChannelConfig.Count + 1];
            FrameSets = new FrameSet[Config.Instance.ChannelConfig.Count + 1];
            _phaseShifters = new IPhaseShifter[] { null }
                .Concat(
                    Config.Instance.ChannelConfig.ChannelNumbers
                        .Select(i => GetChannelDefaultPhaseShifter((byte)i)))
                .ToArray();
            _frameDataNoiseCancellers = new FrameDataCoercer[] { null }
                .Concat(
                    Config.Instance.ChannelConfig.ChannelNumbers
                        .Select(i => GetChannelNoiseCanceller((byte)i)))
                .ToArray();
        }

        public FrameSet[] FrameSets { get; private set; }

        #region IDisposable Members

        public void Dispose()
        {
            if(_dataReceivingThread != null && _dataReceivingThread.IsAlive)
            {
                if(_udpClient != null)
                {
                    _udpClient.Close();
                }
                _dataReceivingThread = null;
            }
            if(_cancellationTokenSource != null)
            {
                _cancellationTokenSource.Dispose();
                _cancellationTokenSource = null;
            }
        }

        #endregion

        /// <summary>
        ///     开启一个新线程接收实时数据。对本方法的调用立刻返回。
        /// </summary>
        public void StartReceivingData()
        {
            _udpClient = CreateUdpClient();
            if(_dataReceivingThread != null)
            {
                throw new InvalidOperationException("The data receiving thread is already started.");
            }
            _cancellationTokenSource = new CancellationTokenSource();
            _dataReceivingThread = new Thread(ReceiveRealtimeDataProc)
            {
                Name = "DataReceivingThread",
                IsBackground = true,
            };
            _dataReceivingThread.Start(_cancellationTokenSource.Token);
        }

        /// <summary>
        ///     停止接收实时数据。
        /// </summary>
        public void StopReceivingData()
        {
            if(_dataReceivingThread != null)
            {
                _udpClient.Close();
                _dataReceivingThread = null;
            }
        }

        private static FrameDataCoercer GetChannelNoiseCanceller(byte channel)
        {
            return new FrameDataNoiseFilter(
                Config.Instance.ChannelConfig[channel].NoiseQuota,
                Config.Instance.DataReadConfig.MinDataValue);
        }

        /// <summary>
        /// 创建UdpClient, 如果端口不可用则等待100ms后重试，若重试20次(2s)仍不可用则抛出异常
        /// </summary>
        /// <returns></returns>
        private static UdpClient CreateUdpClient()
        {
            int tryCount = 0;
            while(true)
            {
                try
                {
                    return new UdpClient(Config.Instance.PathConfig.RealTimePort);
                }
                catch(SocketException)
                {
                    if(++tryCount > 20)
                    {
                        throw;
                    }
                    Thread.Sleep(100);
                }
            }
        }

        private void ReceiveRealtimeDataProc(object cancellationTokenObject)
        {
            var cancellationToken = (CancellationToken)cancellationTokenObject;
            using(var frameReader = GetRealtimeFrameReader())
            {
                while(!cancellationToken.IsCancellationRequested)
                {
                    try
                    {
                        var frame = frameReader.BlockRead();

                        // 发布帧数据事件
                        NewFrameEvent.Instance.Publish(frame);

                        if(frame.Channel == Channel.PhaseChannel)
                        {
                            HandlePhaseFrame(frame);
                        }
                        else
                        {
                            HandleDataFrame(frame);
                        }
                    }
                    catch(SocketException)
                    {
                        // 终止线程执行
                        NotificationEvent.Instance.Publish("端口已关闭；终止接收实时数据。");
                        return;
                    }
                    catch(Exception exception)
                    {
                        // 通知并重试
                        NotificationEvent.Instance.Publish(string.Format("接收实时数据时发生{0}错误。", exception.GetType().Name));
                    }
                }
            }
        }

        private FrameReader GetRealtimeFrameReader()
        {
            // 没有相位转换器，因为该FrameReader会用于读取相位数据
            // 也没有噪声消除器，因为所有通道的数据都在一起
            return new FrameReader(new UdpStream(_udpClient));
        }

        /// <summary>
        ///     处理接收的相位帧，取得其相位起始偏移，并构造对应的相位转换器转换以后该通道收到的数据帧
        /// </summary>
        /// <param name="frame"> </param>
        private void HandlePhaseFrame(Frame frame)
        {
            Config.Instance.ChannelConfig.ForAllChannels(channel =>
            {
                _phaseShifters[channel] = new PhaseShifter(
                    Config.Instance.ChannelConfig[channel].Offset, frame.Data);
            });
        }

        /// <summary>
        ///     处理接收的数据帧。使用通道的相位转换器来转换数据，并尝试加入当前的帧组。 如果加入后帧组满了，就对外发布该帧组。
        /// </summary>
        /// <param name="frame"> </param>
        private void HandleDataFrame(Frame frame)
        {
            var channel = frame.Channel;

            // 相位转换
            _phaseShifters[channel].Shift(frame.Data);

            // 边界强制
            FrameDataMinMaxCoercer.Default.Coerce(frame.Data);

            // 噪声消除
            // TODO: Redundant
            _frameDataNoiseCancellers[channel].Coerce(frame.Data);

            var frameSet = _unfinishedFrameSets[channel];
            if(frameSet == null)
            {
                _unfinishedFrameSets[channel] = new FrameSet(frame,
                    Config.Instance.DataReadConfig.TimestampDeltaQuotaMilliseconds,
                    Config.Instance.DataReadConfig.MinDataValue,
                    Config.Instance.DataReadConfig.MaxDataValue,
                    Config.Instance.ChannelConfig[channel].NoiseQuota);
            }
            else
            {
                var enough = !frameSet.TryInclude(frame);
                if(enough)
                {
                    FrameSets[channel] = frameSet;
                    _unfinishedFrameSets[channel] = null;
                }
            }
        }

        /// <summary>
        ///     使用配置文件的相位偏移值来构造默认的相位转换器。在收到相位数据前，使用该默认相位转换器。
        /// </summary>
        /// <param name="channel"> </param>
        /// <returns> </returns>
        private static IPhaseShifter GetChannelDefaultPhaseShifter(byte channel)
        {
            return new PhaseShifter(Config.Instance.ChannelConfig[channel].Offset, 0);
        }
    }
}